/*-------------------------------------------------------------------------
 * drawElements Quality Program Tester Core
 * ----------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Android utilities.
 *//*--------------------------------------------------------------------*/

#include "tcuAndroidUtil.hpp"

#include "deSTLUtil.hpp"
#include "deMath.h"

#include <vector>

namespace tcu
{
namespace Android
{

using std::string;
using std::vector;

namespace
{

class ScopedJNIEnv
{
public:

					ScopedJNIEnv	(JavaVM* vm);
					~ScopedJNIEnv	(void);

	JavaVM*			getVM			(void) const { return m_vm;		}
	JNIEnv*			getEnv			(void) const { return m_env;	}

private:
	JavaVM* const	m_vm;
	JNIEnv*			m_env;
	bool			m_detach;
};

ScopedJNIEnv::ScopedJNIEnv (JavaVM* vm)
	: m_vm		(vm)
	, m_env		(DE_NULL)
	, m_detach	(false)
{
	const int	getEnvRes	= m_vm->GetEnv((void**)&m_env, JNI_VERSION_1_6);

	if (getEnvRes == JNI_EDETACHED)
	{
		if (m_vm->AttachCurrentThread(&m_env, DE_NULL) != JNI_OK)
			throw std::runtime_error("JNI AttachCurrentThread() failed");

		m_detach = true;
	}
	else if (getEnvRes != JNI_OK)
		throw std::runtime_error("JNI GetEnv() failed");

	DE_ASSERT(m_env);
}

ScopedJNIEnv::~ScopedJNIEnv (void)
{
	if (m_detach)
		m_vm->DetachCurrentThread();
}

class LocalRef
{
public:
					LocalRef		(JNIEnv* env, jobject ref);
					~LocalRef		(void);

	jobject			operator*		(void) const { return m_ref;	}
	operator		bool			(void) const { return !!m_ref;	}

private:
					LocalRef		(const LocalRef&);
	LocalRef&		operator=		(const LocalRef&);

	JNIEnv* const	m_env;
	const jobject	m_ref;
};

LocalRef::LocalRef (JNIEnv* env, jobject ref)
	: m_env(env)
	, m_ref(ref)
{
}

LocalRef::~LocalRef (void)
{
	if (m_ref)
		m_env->DeleteLocalRef(m_ref);
}

void checkException (JNIEnv* env)
{
	if (env->ExceptionCheck())
	{
		env->ExceptionDescribe();
		env->ExceptionClear();
		throw std::runtime_error("Got JNI exception");
	}
}

jclass findClass (JNIEnv* env, const char* className)
{
	const jclass	cls		= env->FindClass(className);

	checkException(env);
	TCU_CHECK_INTERNAL(cls);

	return cls;
}

jclass getObjectClass (JNIEnv* env, jobject object)
{
	const jclass	cls		= env->GetObjectClass(object);

	checkException(env);
	TCU_CHECK_INTERNAL(cls);

	return cls;
}

jmethodID getMethodID (JNIEnv* env, jclass cls, const char* methodName, const char* signature)
{
	const jmethodID		id		= env->GetMethodID(cls, methodName, signature);

	checkException(env);
	TCU_CHECK_INTERNAL(id);

	return id;
}

string getStringValue (JNIEnv* env, jstring jniStr)
{
	const char*		ptr		= env->GetStringUTFChars(jniStr, DE_NULL);
	const string	str		= string(ptr);

	env->ReleaseStringUTFChars(jniStr, ptr);

	return str;
}

string getIntentStringExtra (JNIEnv* env, jobject activity, const char* name)
{
	// \todo [2013-05-12 pyry] Clean up references on error.

	const jclass	activityCls		= getObjectClass(env, activity);
	const LocalRef	intent			(env, env->CallObjectMethod(activity, getMethodID(env, activityCls, "getIntent", "()Landroid/content/Intent;")));
	TCU_CHECK_INTERNAL(intent);

	const LocalRef	extraName		(env, env->NewStringUTF(name));
	const jclass	intentCls		= getObjectClass(env, *intent);
	TCU_CHECK_INTERNAL(extraName && intentCls);

	jvalue getExtraArgs[1];
	getExtraArgs[0].l = *extraName;

	const LocalRef	extraStr		(env, env->CallObjectMethodA(*intent, getMethodID(env, intentCls, "getStringExtra", "(Ljava/lang/String;)Ljava/lang/String;"), getExtraArgs));

	if (extraStr)
		return getStringValue(env, (jstring)*extraStr);
	else
		return string();
}

void setRequestedOrientation (JNIEnv* env, jobject activity, ScreenOrientation orientation)
{
	const jclass	activityCls			= getObjectClass(env, activity);
	const jmethodID	setOrientationId	= getMethodID(env, activityCls, "setRequestedOrientation", "(I)V");

	env->CallVoidMethod(activity, setOrientationId, (int)orientation);
}

template<typename Type>
const char* getJNITypeStr (void);

template<>
const char* getJNITypeStr<int> (void)
{
	return "I";
}

template<>
const char* getJNITypeStr<deInt64> (void)
{
	return "J";
}

template<>
const char* getJNITypeStr<string> (void)
{
	return "Ljava/lang/String;";
}

template<>
const char* getJNITypeStr<vector<string> > (void)
{
	return "[Ljava/lang/String;";
}

template<typename FieldType>
FieldType getStaticFieldValue (JNIEnv* env, jclass cls, jfieldID fieldId);

template<>
int getStaticFieldValue<int> (JNIEnv* env, jclass cls, jfieldID fieldId)
{
	DE_ASSERT(cls && fieldId);
	return env->GetStaticIntField(cls, fieldId);
}

template<>
string getStaticFieldValue<string> (JNIEnv* env, jclass cls, jfieldID fieldId)
{
	const jstring	jniStr	= (jstring)env->GetStaticObjectField(cls, fieldId);

	if (jniStr)
		return getStringValue(env, jniStr);
	else
		return string();
}

template<>
vector<string> getStaticFieldValue<vector<string> > (JNIEnv* env, jclass cls, jfieldID fieldId)
{
	const jobjectArray	array		= (jobjectArray)env->GetStaticObjectField(cls, fieldId);
	vector<string>		result;

	checkException(env);

	if (array)
	{
		const int	numElements		= env->GetArrayLength(array);

		for (int ndx = 0; ndx < numElements; ndx++)
		{
			const jstring	jniStr	= (jstring)env->GetObjectArrayElement(array, ndx);

			checkException(env);

			if (jniStr)
				result.push_back(getStringValue(env, jniStr));
		}
	}

	return result;
}

template<typename FieldType>
FieldType getStaticField (JNIEnv* env, const char* className, const char* fieldName)
{
	const jclass	cls			= findClass(env, className);
	const jfieldID	fieldId		= env->GetStaticFieldID(cls, fieldName, getJNITypeStr<FieldType>());

	checkException(env);

	if (fieldId)
		return getStaticFieldValue<FieldType>(env, cls, fieldId);
	else
		throw std::runtime_error(string(fieldName) + " not found in " + className);
}

template<typename FieldType>
FieldType getFieldValue (JNIEnv* env, jobject obj, jfieldID fieldId);

template<>
deInt64 getFieldValue<deInt64> (JNIEnv* env, jobject obj, jfieldID fieldId)
{
	DE_ASSERT(obj && fieldId);
	return env->GetLongField(obj, fieldId);
}

template<typename FieldType>
FieldType getField (JNIEnv* env, jobject obj, const char* fieldName)
{
	const jclass	cls			= getObjectClass(env, obj);
	const jfieldID	fieldId		= env->GetFieldID(cls, fieldName, getJNITypeStr<FieldType>());

	checkException(env);

	if (fieldId)
		return getFieldValue<FieldType>(env, obj, fieldId);
	else
		throw std::runtime_error(string(fieldName) + " not found in object");
}

void describePlatform (JNIEnv* env, std::ostream& dst)
{
	const char* const	buildClass		= "android/os/Build";
	const char* const	versionClass	= "android/os/Build$VERSION";

	static const struct
	{
		const char*		classPath;
		const char*		className;
		const char*		fieldName;
	} s_stringFields[] =
	{
		{ buildClass,	"Build",			"BOARD"			},
		{ buildClass,	"Build",			"BRAND"			},
		{ buildClass,	"Build",			"DEVICE"		},
		{ buildClass,	"Build",			"DISPLAY"		},
		{ buildClass,	"Build",			"FINGERPRINT"	},
		{ buildClass,	"Build",			"HARDWARE"		},
		{ buildClass,	"Build",			"MANUFACTURER"	},
		{ buildClass,	"Build",			"MODEL"			},
		{ buildClass,	"Build",			"PRODUCT"		},
		{ buildClass,	"Build",			"TAGS"			},
		{ buildClass,	"Build",			"TYPE"			},
		{ versionClass,	"Build.VERSION",	"RELEASE"		},
	};

	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_stringFields); ndx++)
		dst << s_stringFields[ndx].className << "." << s_stringFields[ndx].fieldName
			<< ": " << getStaticField<string>(env, s_stringFields[ndx].classPath, s_stringFields[ndx].fieldName)
			<< "\n";

	dst << "Build.VERSION.SDK_INT: " << getStaticField<int>(env, versionClass, "SDK_INT") << "\n";

	{
		const vector<string>	supportedAbis	= getStaticField<vector<string> >(env, buildClass, "SUPPORTED_ABIS");

		dst << "Build.SUPPORTED_ABIS: ";

		for (size_t ndx = 0; ndx < supportedAbis.size(); ndx++)
			dst << (ndx != 0 ? ", " : "") << supportedAbis[ndx];

		dst << "\n";
	}
}

} // anonymous

ScreenOrientation mapScreenRotation (ScreenRotation rotation)
{
	switch (rotation)
	{
		case SCREENROTATION_UNSPECIFIED:	return SCREEN_ORIENTATION_UNSPECIFIED;
		case SCREENROTATION_0:				return SCREEN_ORIENTATION_PORTRAIT;
		case SCREENROTATION_90:				return SCREEN_ORIENTATION_LANDSCAPE;
		case SCREENROTATION_180:			return SCREEN_ORIENTATION_REVERSE_PORTRAIT;
		case SCREENROTATION_270:			return SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
		default:
			print("Warning: Unsupported rotation");
			return SCREEN_ORIENTATION_PORTRAIT;
	}
}

string getIntentStringExtra (ANativeActivity* activity, const char* name)
{
	const ScopedJNIEnv	env(activity->vm);

	return getIntentStringExtra(env.getEnv(), activity->clazz, name);
}

void setRequestedOrientation (ANativeActivity* activity, ScreenOrientation orientation)
{
	const ScopedJNIEnv	env(activity->vm);

	setRequestedOrientation(env.getEnv(), activity->clazz, orientation);
}

void describePlatform (ANativeActivity* activity, std::ostream& dst)
{
	const ScopedJNIEnv	env(activity->vm);

	describePlatform(env.getEnv(), dst);
}

size_t				getTotalAndroidSystemMemory		(ANativeActivity* activity)
{
	const ScopedJNIEnv	scopedJniEnv				(activity->vm);
	JNIEnv* env = scopedJniEnv.getEnv();

	// Get activity manager instance:
	// ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
	const jclass	activityManagerClass	= findClass(env, "android/app/ActivityManager");
	const LocalRef	activityString			(env, env->NewStringUTF("activity")); // Context.ACTIVITY_SERVICE == "activity"
	const jclass	activityClass			= getObjectClass(env, activity->clazz);
	const jmethodID	getServiceID			= getMethodID(env, activityClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
	LocalRef		activityManager			(env, env->CallObjectMethod(activity->clazz, getServiceID, *activityString));
	checkException(env);
	TCU_CHECK_INTERNAL(activityManager);

	// Crete memory info instance:
	// ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
	const jclass	memoryInfoClass			= findClass(env, "android/app/ActivityManager$MemoryInfo");
	const jmethodID	memoryInfoCtor			= getMethodID(env, memoryInfoClass, "<init>", "()V");
	LocalRef		memoryInfo				(env, env->NewObject(memoryInfoClass, memoryInfoCtor));
	checkException(env);
	TCU_CHECK_INTERNAL(memoryInfo);

	// Get memory info from activity manager:
	// activityManager.getMemoryInfo(memoryInfo);
	const jmethodID	getMemoryInfoID			= getMethodID(env, activityManagerClass, "getMemoryInfo", "(Landroid/app/ActivityManager$MemoryInfo;)V");
	checkException(env);
	env->CallVoidMethod(*activityManager, getMemoryInfoID, *memoryInfo);

	// Return 'totalMem' field from the memory info instance.
	return static_cast<size_t>(getField<deInt64>(env, *memoryInfo, "totalMem"));
}

} // Android
} // tcu
